package hibernate.patterns;

import static hibernate.patterns.ClassUtils.getGenericTypeArgument;
import org.hibernate.HibernateException;
import org.hibernate.type.NullableType;
import org.hibernate.type.TypeFactory;
import org.hibernate.usertype.UserType;

import java.io.Serializable;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Types;

public abstract class UserTypeBase<TModelType, TBaseType> implements UserType {
    private Class modelType;
    private NullableType dbType;

    public UserTypeBase() {
        modelType = getGenericTypeArgument(getClass());
        dbType = (NullableType) TypeFactory.basic(getGenericTypeArgument(getClass()).getCanonicalName());
    }

    public Class<TModelType> returnedClass() {
        return modelType;
    }

    public Object nullSafeGet(ResultSet resultSet, String[] names, Object owner) throws HibernateException, SQLException {
        Object value = dbType.get(resultSet, names[0]);
        if (value == null) {
            return getNullValue();
        }
        return createObject(convertSqlToBaseType(value));
    }

    public void nullSafeSet(PreparedStatement preparedStatement, Object value, int index) throws HibernateException, SQLException {
        if (value == null || isNullObject(value.getClass())) {
            preparedStatement.setObject(index, Types.NULL);
            return;
        }
        TModelType objectToSet = (TModelType) value;
        preparedStatement.setObject(index, convertModelToBaseType(objectToSet));
    }

    public boolean equals(Object x, Object y) throws HibernateException {
        if (x == null || y == null) {
            return x == y;
        }
        if (!x.getClass().isAssignableFrom(modelType) || !y.getClass().isAssignableFrom(modelType)) {
            return false;
        }
        return x.equals(y);
    }

    public abstract int[] sqlTypes();

    protected abstract TModelType createObject(TBaseType baseValue);

    protected abstract TBaseType convertSqlToBaseType(Object value);

    protected abstract TModelType getNullValue();

    protected abstract TBaseType convertModelToBaseType(TModelType objectToSet);

    public int hashCode(Object x) throws HibernateException {
        return x.hashCode();
    }

    public Object deepCopy(Object x) throws HibernateException {
        throw new UnsupportedOperationException("FIXME");
    }

    public boolean isMutable() {
        return false;
    }

    public Serializable disassemble(Object x) throws HibernateException {
        throw new UnsupportedOperationException("FIXME");
    }

    public Object assemble(Serializable cached, Object owner) throws HibernateException {
        throw new UnsupportedOperationException("FIXME");
    }

    public Object replace(Object original, Object target, Object owner) throws HibernateException {
        throw new UnsupportedOperationException("FIXME");
    }

    private boolean isNullObject(Class clazz) {
        return clazz.isAnnotationPresent(NullType.class);
    }
}
